# -*- coding: UTF-8 -*-

import time, json, traceback
import socket
import os
import logging
import threading
import addon_grpc_def
import addon_exception_code
import pylog


grpc_addon_logger = pylog.grpc_addon_logger


class addon_controller:

    __ADDON_CMD_CH = addon_grpc_def.ADDON_CMD_CH
    __ADDON_LOCAL_ADDR = addon_grpc_def.ADDON_UNIX_COM_PREFIX + ".addon_grpc." + str(os.getpid())
    __ADDON_LOCAL_STAT_ADDR = addon_grpc_def.ADDON_UNIX_COM_PREFIX + ".stat_addon_grpc." + str(os.getpid())
    __SOCK_BUF_SIZE = 8192

    def __init__(self, c, etype):
        self.c = c
        self.etype = etype
        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
        self.stat_sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)

        self.filename = self.__bind_sock(self.sock, self.__ADDON_LOCAL_ADDR)
        self.stat_filename = self.__bind_sock(self.stat_sock, self.__ADDON_LOCAL_STAT_ADDR)

        self.__progress = 0
        self.__async_result = addon_exception_code.RES_SUCC
        self.__mutex = threading.Lock()
        self.__update_event = threading.Event()
        self.__td = threading.Thread(target=self.__update_install_progress)
        self.__td.setDaemon(True)
        self.__td.start()

        grpc_addon_logger().info("addon_controller_client.__init__ run ! {}".format(os.getpid()))

    def __bind_sock(self, sock, addr):
        sock.bind(addr)

    def __reset_async_stat(self):
        self.__progress = 0
        self.__async_result = addon_exception_code.RES_SUCC
        self.__update_event.set()

    def __send_json_req(self, req):
        request = json.dumps(req)
        grpc_addon_logger().debug("request: %s", request)
        self.sock.sendto(request, self.__ADDON_CMD_CH)

    def __parse_res_msg(self, res_msg):
        data = json.loads(res_msg)
        return data["cmd"], data["code"], data["data"], data["msg"]

    def __wait_res(self, cmd):
        res_msg, server_addr = self.sock.recvfrom(self.__SOCK_BUF_SIZE)
        res_cmd, code, data, msg = self.__parse_res_msg(res_msg)

        if cmd == res_cmd:
            res = code
            return res, data, msg
        else:
            return None, None, ""

    def __call(self, cmd, param):
        """
        @param cmd: int 执行的命令编号
        @param param: str 指定命令需要的参数,是一个dict
        @return tuple(res, data, msg)
            res: 返回码
            data: 返回数据
            msg: 错误消息
        """
        try:
            req = {"cmd": cmd, "param": param}
            try:
                self.__mutex.acquire()
                self.__send_json_req(req)
                res, data, msg = self.__wait_res(cmd)
                self.set_heartbeat_ecode(res, msg)
            finally:
                self.__mutex.release()

        except socket.error as e:
            if e.errno == socket.errno.ECONNREFUSED:
                grpc_addon_logger().error("addon controller server is not runnning")
            else:
                traceback.print_exc()
            return None, None, ""

        except Exception as e:
            traceback.print_exc()
            return None, None, ""

        else:
            if cmd not in [addon_grpc_def.CMD_GET_ALIVE_STATUS]:
                grpc_addon_logger().debug("response: {res: %s, data: %s, msg: %s}", res, data, msg)
            return res, data, msg

    def start(self, service_path):
        param = {"service_path": service_path}
        return self.__call(addon_grpc_def.CMD_START, param)

    def stop(self, service_path):
        param = {"service_path": service_path}
        return self.__call(addon_grpc_def.CMD_STOP, param)

    def stop_all(self):
        return self.__call(addon_grpc_def.CMD_STOPALL, {})

    def get_service_status(self):
        return self.__call(addon_grpc_def.CMD_GET_ALIVE_STATUS, {})

    def install(self, ftppath):
        param = {"ftppath": ftppath}
        return self.__call(addon_grpc_def.CMD_INSTALL, param)

    def install_async(self, ftppath):
        param = {"ftppath": ftppath}
        res, data, msg = self.__call(addon_grpc_def.CMD_INSTALL_ASYNC, param)
        if res == addon_exception_code.RES_SUCC:
            self.__reset_async_stat()
        return res, data, msg

    def get_addon_list(self):
        return self.__call(addon_grpc_def.CMD_GET_ADDON_LIST, {})

    def delete_addon(self, service_path):
        param = {"service_path": service_path}
        return self.__call(addon_grpc_def.CMD_DELETE_ADDON, param)

    def pack_addon_async(self, addon_filename):
        param = {"filename": addon_filename}
        res, data, msg = self.__call(addon_grpc_def.CMD_PACK_ADDON_ASYNC, param)
        if res == addon_exception_code.RES_SUCC:
            self.__reset_async_stat()
        return res, data, msg

    def pack_addon(self, addon_filename):
        param = {"filename": addon_filename}
        return self.__call(addon_grpc_def.CMD_PACK_ADDON, param)

    def convert_addon(self):
        return self.__call(addon_grpc_def.CMD_CONVERT_ADDON, {})

    def __get_install_status(self):
        """
        获得安装或下载时的打包进度
        未在安装过程中尝试获取安装进度会通过res返回错误码(EXCEPTION_SERVICE_INSTALL_PACKAGE_NO_INSTALL_TASK)
        安装过程中收到100意味着安装完成, 未到100通过res返回收到的错误码即为安装失败的原因
        """
        return self.__call(addon_grpc_def.CMD_GET_INSTALL_STATUS, {})

    def __update_install_progress(self):
        """
        连续获得安装进度或下载时的打包进度
        """
        while True:
            self.__update_event.wait()
            res, status, _ = self.__get_install_status()
            if status is None:
                grpc_addon_logger().error("__update_install_progress (%s %s)", res, status)
            if res == addon_exception_code.RES_SUCC:
                self.__progress = status["progress"]
                if self.__progress == 100:
                    self.__update_event.clear()
                time.sleep(1)
            else:
                grpc_addon_logger().error("__update_install_progress failed (%s)", res)
                self.__async_result = res
                self.__update_event.clear()

    def get_install_status(self):
        return self.__async_result, self.__progress

    def set_heartbeat_ecode(self, err_code, err_msg):
        if err_code == addon_exception_code.RES_SUCC:
            return

        errcode_map = {
            addon_exception_code.EXCEPTION_SERVICE_NUM_CONSTRAINT: 0x0F0116,
            addon_exception_code.EXCEPTION_SERVICE_NO_SUCH_ADDON_SERVICE: 0x0F0117,
            addon_exception_code.EXCEPTION_SERVICE_GET_USEABLE_PORT_LIST_FAILED: 0x0F0118,
            addon_exception_code.EXCEPTION_SERVICE_ALLOCATE_PORT_FAILED: 0x0F0119,
            addon_exception_code.EXCEPTION_SERVICE_OPERATE_RUNNING_ADDON: 0x0F011A,
            addon_exception_code.EXCEPTION_SERVICE_OPERATE_STOPPED_ADDON: 0x0F011B,
            addon_exception_code.EXCEPTION_SERVICE_NO_SUC_ADDON_CTRL_CMD: 0x0F011C,
            addon_exception_code.EXCEPTION_SERVICE_BUILTINADDON_PORT_DUPLICATED: 0x0F011D,
            addon_exception_code.EXCEPTION_SERVICE_BUILTINADDON_OP_FAILED: 0x0F011F,
            addon_exception_code.EXCEPTION_SERVICE_OPERATE_INVALID_ADDON: 0x0F0120,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_INVALID: 0x0F0121,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_MISS_REQUIRED: 0x0F0122,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_ERR_PORT: 0x0F0123,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_ERR_LANGUAGE: 0x0F0124,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_ERR_TYPE: 0x0F0125,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_ERR_SERVICEENABLE: 0x0F0126,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_ERR_SERVICE: 0x0F0127,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_ERR_NAME: 0x0F0128,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_ERR_DESCRIPTION: 0x0F0129,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_ERR_URL: 0x0F012A,
            addon_exception_code.EXCEPTION_SERVICE_CONFIG_FILE_ERR_CONVENTION: 0x0F012B,
            addon_exception_code.EXCEPTION_SERVICE_INSTALL_PACKAGE_NOT_EXIST: 0x0F012C,
            addon_exception_code.EXCEPTION_SERVICE_INSTALL_PACKAGE_MOVE_FAILED: 0x0F012D,
            addon_exception_code.EXCEPTION_SERVICE_INSTALL_PACKAGE_UNCOMPRESS_FAILED: 0x0F012E,
            addon_exception_code.EXCEPTION_SERVICE_INSTALL_PACKAGE_THREAD_IS_BUSY: 0x0F012F,
            addon_exception_code.EXCEPTION_SERVICE_INSTALL_PACKAGE_NO_INSTALL_TASK: 0x0F0130,
            addon_exception_code.EXCEPTION_SERVICE_INSTALL_PACKAGE_COMPRESS_FAILED: 0x0F0131,
            addon_exception_code.EXCEPTION_SERVICE_INSTALL_PACKAGE_TIMEOUT: 0x0F0132,
            addon_exception_code.EXCEPTION_SERVICE_INSTALL_PACKAGE_WRONG_FORMAT: 0x0F0133,
        }
        heartbeat_errcode = errcode_map.get(err_code)
        if heartbeat_errcode is None:
            pass
            # self.c.set_grpc_errcode(self.etype, 0x0FFFFF, "unknown addon errcode,", 0xff)
        else:
            self.c.set_grpc_errcode(self.etype, heartbeat_errcode, err_msg, 0xFF, 0)


__all__ = ["addon_controller", "addon_grpc_def", "addon_exception_code"]

if __name__ == "__main__":
    ac = addon_controller()

    def Test0():
        while True:
            try:
                print("start !!!")
                res = ac.start("/aaa/bbb/ccc")
                print("start !!! ({})".format(res))
                print("stop !!!")
                ac.stop("/bbb/bbb/ccc")
                print("stop all !!!")
                ac.stop_all()
                print("get_service_status !!!")
                ac.get_service_status()
                time.sleep(1)
            except:
                time.sleep(1)
                traceback.print_exc()

    def Test1():
        service_path = "/usr/etc/jkzuc/configs/JAKA/AddOns/io_event_triger/io_event_triger.py"
        # res = ac.start(service_path)
        # res = ac.stop('/usr/etc/jkzuc/configs/JAKA/AddOns/io_event_triger/io_event_triger.py')
        time.sleep(1)
        ac.stop(service_path)
        pass

    # Test1()

    def Test_Install():
        ftppath = "/usr/etc/jkzuc/scripts/io_event_triger.tar.gz"
        ac.install(ftppath)

    # Test_Install()

    def Test_GetAddOnList():
        ret_code, ret_data = ac.get_addon_list()
        print("[Test_GetAddOnList] get_addon_list'res is {}".format(ret_data))
        for addoninfo in ret_data:
            print("addoninfo: {}".format(addoninfo))

    # Test_GetAddOnList()
